
import { childrenBuilder, EOChild } from "../mve/childrenBuilder"
import { buildElement, parseUtil } from "../mve/index"
import { arrayInit, BuildResult, alawaysChange, getAsOne, mve, notEqualChange, onceLife } from "../mve/util"
import { VirtualChildParam } from "../mve/virtualTreeChildren"
import * as DOM from "./DOM"
/**
 * 在DOM里，移动其实是少用的
 * 比如窗口，使用z-index的方式，不使用移动
 * 别的表格拖拽，似乎更是根据模型在相应区域重新生成视图
 */
export class DOMVirtualParam implements VirtualChildParam<Node>{
	constructor(
		private pel: Node
	) { }
	append(el: Node, isMove: boolean) {
		DOM.appendChild(this.pel, el, isMove)
	}
	remove(el: Node) {
		DOM.removeChild(this.pel, el)
	}
	insertBefore(el: Node, oldEl: Node, isMove: boolean) {
		DOM.insertChildBefore(this.pel, el, oldEl, isMove)
	}
}
export class DOMVirtualParamReverse implements VirtualChildParam<Node>{
	constructor(
		private pel: Node
	) { }
	remove(e: Node): void {
		DOM.removeChild(this.pel, e)
	}
	append(e: Node, isMove?: boolean): void {
		DOM.prefixChild(this.pel, e, isMove)
	}
	insertBefore(e: Node, old: Node, isMove?: boolean): void {
		DOM.insertChildAfter(this.pel, e, old, isMove)
	}
}

export type StringValue = mve.MTValue<string>
export type ItemValue = mve.MTValue<string | number | boolean>
export interface AttrMap {
	[key: string]: mve.MTValue<string | number | boolean | void>
}
export type PropMap = {
	[key: string]: mve.MTValue<string | number | boolean>
}
export interface BaseStyleMap {
	[key: string]: string | number
}
export interface StyleMap {
	[key: string]: mve.MTValue<BaseStyleMap[string]>
}
export type EventHandler = (e: any) => void
/**动作树 */
export type EventItem = EventHandler | {
	capture?: boolean
	handler: EventHandler
} | EventItem[]
export type EventMap = { [key: string]: EventItem }
export function reWriteEvent(n: EventMap, eventName: string, fun: (vs: EventItem[]) => EventItem[]) {
	const v = n[eventName]
	if (mb.Array.isArray(v)) {
		n[eventName] = fun(v)
	} else
		if (v) {
			n[eventName] = fun([v])
		} else {
			n[eventName] = fun([])
		}
}




export type InitFun = (v: any, me: mve.LifeModel) => (void | DestoryFun)
export function reWriteInit(v: DOMNode, fun: (vs: InitFun[]) => InitFun[]) {
	if (mb.Array.isArray(v.init)) {
		v.init = fun(v.init)
	} else
		if (v.init) {
			v.init = fun([v.init])
		} else {
			v.init = fun([])
		}
}
export type DestoryFun = () => void
export type DOMNodeChildren = EOChild<Node>[] | EOChild<Node>
export interface DOMNode {
	type: string
	init?: InitFun | InitFun[]
	id?: StringValue
	cls?: mve.MTValue<string | string[]>
	attr?: AttrMap
	style?: StyleMap | mve.GValue<BaseStyleMap>
	prop?: PropMap
	event?: EventMap

	value?: ItemValue
	text?: ItemValue
	children?: DOMNodeChildren
	childrenReverse?: boolean
}
function addEvent(el: Node, event: EventMap) {
	if (event) {
		for (const k in event) {
			const v = event[k]
			if (mb.Array.isArray(v)) {
				for (const vv of v) {
					DOM.event(el, k, vv)
				}
			} else {
				DOM.event(el, k, v)
			}
		}
	}
}
function arrayFromInit(configInit?: InitFun | InitFun[]) {
	return configInit ? (Array.isArray(configInit) ? configInit : [configInit]) : []
}
function buildParam(me: mve.LifeModel, el: Node, config: DOMNode) {
	const attr = config.attr || {}

	addEvent(el, config.event)
	if (config.cls) {
		if (attr['class']) {
			console.warn('仅使用外层cls而不使用内层class')
			delete attr['class']
		}
		parseUtil.bind(me, config.cls, function (v) {
			const cls = mb.Array.isArray(v) ? v.join(' ') : v
			DOM.attr(el, "class", cls)
		}, notEqualChange)
	}

	if (config.style) {
		if (attr.style) {
			console.warn("仅使用顶层的style而不使用attr.style", config)
			delete attr.style
		}
		if (typeof (config.style) == 'function') {
			parseUtil.bind(me, config.style, function (v) {
				DOM.attr(el, 'style', styleStringify(v))
			}, notEqualChange)
		} else {
			parseUtil.bindKV(me, config.style, function (k, v) {
				DOM.style(el, k, v)
			}, notEqualChange)
		}
	}

	if (config.id) {
		if (attr.id) {
			console.warn('仅使用外层id而不使用内层attr.id')
		}
		attr.id = config.id
	}

	/**attr属性初始化*/
	parseUtil.bindKV(me, attr, function (k, v) {
		DOM.attr(el, k, v)
	}, notEqualChange)

	const inits = config.init ? (mb.Array.isArray(config.init) ? config.init : [config.init]) : []

	const prop = config.prop || {}

	const children = config.children ? (mb.Array.isArray(config.children) ? config.children : [config.children]) : []
	const hasChildren = children.length > 0
	if (hasChildren) {
		if (config.text) {
			console.warn('有children属性不能有text', config)
			delete config.text
		}
	} else {
		if (config.text) {
			parseUtil.bind(me, config.text, function (v) {
				DOM.content(el, v)
			}, notEqualChange)
		}
	}
	/***prop属性初始化 */
	if (prop.scrollTop) {
		const scrollTop = prop.scrollTop
		delete prop.scrollTop
		inits.unshift(function () {
			parseUtil.bind(me, scrollTop, function (v) {
				DOM.prop(el, 'scrollTop', v)
			}, notEqualChange)
		})
	}
	if (prop.scrollLeft) {
		const scrollLeft = prop.scrollLeft
		delete prop.scrollLeft
		inits.unshift(function () {
			parseUtil.bind(me, scrollLeft, function (v) {
				DOM.prop(el, 'scrollLeft', v)
			}, notEqualChange)
		})
	}
	parseUtil.bindKV(me, prop, function (k, v) {
		DOM.prop(el, k, v)
	}, alawaysChange)

	if (hasChildren) {
		const virtualParam = config.childrenReverse ? new DOMVirtualParamReverse(el) : new DOMVirtualParam(el)
		const childrenInit = childrenBuilder(me, virtualParam, children)
		if (childrenInit) {
			//子层先初始化
			inits.unshift(childrenInit)
		}
	}

	/**
	 * value必须在Attr后面才行，不然type=range等会无效
	 * select的value必须放在children后，不然会无效
	 */
	if (config.value) {
		parseUtil.bind(me, config.value, function (v) {
			DOM.value(el, v)
		}, alawaysChange)
	}

	return {
		element: el,
		init: getInit(inits, el, me)
	}
}

function getInit(inits: InitFun[], el: Node, me: mve.LifeModel) {
	if (inits.length > 0) {
		const init = function (v: InitFun) {
			const destroy = v(el, me)
			if (destroy) {
				if (typeof (destroy) == 'function') {
					return destroy
				} else {
					console.warn(`返回desroy不为函数，不视为生命周期函数`, destroy, v)
				}
			}
		}
		return function () {
			return arrayInit(inits, init)
		}
	}
}

export const dom = buildElement(function (me: mve.LifeModel, p: DOMNode) {
	const element = DOM.createElement(p.type)
	return buildParam(me, element, p)
})
export const svg = buildElement(function (me: mve.LifeModel, p: DOMNode) {
	const element = DOM.createElementNS(p.type, "http://www.w3.org/2000/svg")
	return buildParam(me, element, p)
})

export interface DOMTextNode {
	text: ItemValue
	init?: InitFun | InitFun[]
	event?: EventMap
}
export const domText = buildElement(function (me: mve.LifeModel, p: DOMTextNode) {
	const element = DOM.createTextNode("")
	parseUtil.bind(me, p.text, function (v) {
		element.textContent = v
	}, notEqualChange)
	const inits = arrayFromInit(p.init)
	addEvent(element, p.event)
	return {
		init: getInit(inits, element, me),
		element: element as Node
	}
})

let idCount = 0
/**生成唯一ID*/
export function idOf(name: string) {
	return name + (idCount++)
}
let clsCount = 0
/**生成唯一class */
export function clsOf(name: string) {
	return name + (clsCount++)
}

export function styleStringify(base: BaseStyleMap) {
	const os: string[] = []
	mb.Object.forEach(base, function (v, k) {
		os.push(`${k}:${v};`)
	})
	return os.join('')
}

export interface StyleBuilder {
	(v: BaseStyleMap): BaseStyleMap
}
/**
 * 像类一样在attr.style上组合
 * @param vs 
 * @returns 
 */
export function cascadingStyle(...vs: StyleBuilder[]) {
	return () => {
		let base: BaseStyleMap = {}
		for (let i = 0; i < vs.length; i++) {
			base = vs[i](base)
		}
		return base
	}
}